package daos;

import java.io.Serializable;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;

import javax.persistence.EntityManager;
import javax.persistence.EntityManagerFactory;
import javax.persistence.NoResultException;
import javax.persistence.Persistence;
import javax.persistence.Query;
import javax.persistence.criteria.CriteriaQuery;


public abstract class GenericDAO<T> implements Serializable {
	private static final long serialVersionUID = 1L;

	private static final EntityManagerFactory emf = Persistence
			.createEntityManagerFactory("clinic");
	protected EntityManager em;

	private Class<T> entityClass;

	public void flush() {
		em.flush();
	}

	public GenericDAO(Class<T> entityClass) {
		this.entityClass = entityClass;
		this.em = emf.createEntityManager();
	}

	public void save(T entity) {
		try {
			em.getTransaction().begin();
			if (!em.contains(entity)) {
				em.persist(entity);
				em.flush();
			}
			em.getTransaction().commit();
		} catch (Exception e) {
			em.getTransaction().rollback();
		}

	}

	public void delete(T entity) {
		try {
			em.getTransaction().begin();
			if (!em.contains(entity)) {
				em.remove(entity);
				em.flush();
			}
			em.getTransaction().commit();
		} catch (Exception e) {
			em.getTransaction().rollback();
		}

	}

	public T update(T entity) {
		try {
			em.getTransaction().begin();
			em.merge(entity);
			em.flush();
			em.getTransaction().commit();
			return entity;
		} catch (Exception e) {
			em.getTransaction().rollback();
		}
		return entity;
	}

	public T find(long entityID) {
		return em.find(entityClass, entityID);
	}

	public T findReferenceOnly(long entityID) {
		return em.getReference(entityClass, entityID);
	}

	// Using the unchecked because JPA does not have a
	// em.getCriteriaBuilder().createQuery()<T> method
	@SuppressWarnings({ "unchecked", "rawtypes" })
	public List<T> findAll() {
		CriteriaQuery cq = em.getCriteriaBuilder().createQuery();
		cq.select(cq.from(entityClass));
		return em.createQuery(cq).getResultList();
	}

	// Using the unchecked because JPA does not have a
	// query.getSingleResult()<T> method
	@SuppressWarnings("unchecked")
	protected T findOneResult(String namedQuery, Map<String, Object> parameters) {
		T result = null;

		try {
			if (em != null) {
				Query query = em.createNamedQuery(namedQuery);

				// Method that will populate parameters if they are passed not
				// null and empty
				if (parameters != null && !parameters.isEmpty()) {
					populateQueryParameters(query, parameters);
				}

				result = (T) query.getSingleResult();
			}
		} catch (NoResultException e) {
			System.out
					.println("No result found for named query: " + namedQuery);
		} catch (Exception e) {
			System.out.println("Error while running query: " + e.getMessage());
			e.printStackTrace();
		}

		return result;
	}

	@SuppressWarnings("unchecked")
	protected List<T> findAllResults(String namedQuery,
			Map<String, Object> parameters) {
		List<T> result = null;

		try {
			if (em != null) {
				Query query = em.createNamedQuery(namedQuery);

				// Method that will populate parameters if they are passed not
				// null and empty
				if (parameters != null && !parameters.isEmpty()) {
					populateQueryParameters(query, parameters);
				}

				result = query.getResultList();
			}
		} catch (NoResultException e) {
			System.out
					.println("No result found for named query: " + namedQuery);
		} catch (Exception e) {
			System.out.println("Error while running query: " + e.getMessage());
			e.printStackTrace();
		}

		return result;
	}

	private void populateQueryParameters(Query query,
			Map<String, Object> parameters) {
		for (Entry<String, Object> entry : parameters.entrySet()) {
			query.setParameter(entry.getKey(), entry.getValue());
		}
	}

	public T delete(long id) {
		try {
			em.getTransaction().begin();

			T entity = find(id);
			if (!em.contains(entity)) {
				em.remove(entity);
				em.flush();
			}
			em.getTransaction().commit();
			return null;
		} catch (Exception e) {
			em.getTransaction().rollback();
			return null;
		}
	}
}
